/* 
发布订阅设计模式 
   @1 灵感来源：参照DOM2中的事件池机制，实现事件/方法的管理「加入事件池、移除事件池、通知事件池中的方法执行」；只不过浏览器的内置事件池，只能对浏览器标准事件进行管理，也只有内置事件池具备自动通知执行的机制！！

   @2 设计思路：对于一些“自定义事件”，我们可以自己参照浏览器内置事件池的机制，对其进行管理
     + 「发布」我们自己创建一个自定义事件池（也就是一个容器，可以是一个对象，也可以是一个数组）
     + 「订阅」基于 on 方法，向自定义事件池中加入信息「信息包含：自定义事件的名字、绑定的方法」
     + 「移除订阅」基于 off 方法，把信息从自定义事件池中移除
     + 「通知」基于 emit 方法，把自定义事件池中，指定的自定义事件通知执行「也就是把其绑定的方法执行」
    这样的操作就是“发布订阅设计模式”，主要用来解决“自定义事件”的问题！！

   @3 应用场景：
     + 在Vue实现组件通信的方式中，有一种就是利用发布订阅设计模式实现的
       <Child @beforeClose="handle">
         + Child子组件有一个自己的自定义事件池
         + @beforeClose="handle" 向事件池中，注入一个‘beforeClose’的自定义事件，绑定的方法是 handle
         + 在子组件内部，我们可以基于 $emit('beforeClose',10) 通知自定义事件执行，也就是把 handle 方法执行，传递实参 10
         基于这种方式实现：子改父
       <Child v-model="active">
         首先对v-model变形一下：<Child :value="active" @input="e=>active=e">
         + 把active状态值作为 value 属性传递给子组件
         + 给子组件的事件池中，注入一个 input 自定义事件，绑定一个方法
         + 在子组件内部，可以基于 value 属性获取传递的状态值，也可以基于 $emit('input',false) 通知自定义事件执行「把父组件的active状态值改为false」
       <Child v-model:show="active">
       <Child :active.sync="active">
       ...
     + 在 react 的 redux 源码中，也是用到了发布订阅
     + ...
*/

/* 
let listeners = {}
// 向事件池中加入自定义事件及方法
const on = function on(name, callback) {
    if (typeof name !== 'string') throw new TypeError('name is not a string')
    if (typeof callback !== 'function') throw new TypeError('callback is not a function')
    if (!listeners.hasOwnProperty(name)) listeners[name] = []
    let arr = listeners[name]
    if (arr.includes(callback)) return
    arr.push(callback)
}
// 从事件池中移除自定义事件及方法
const off = function off(name, callback) {
    if (typeof name !== 'string') throw new TypeError('name is not a string')
    if (typeof callback !== 'function') throw new TypeError('callback is not a function')
    let arr = listeners[name],
        index
    if (!Array.isArray(arr)) return
    index = arr.indexOf(callback)
    if (index >= 0) arr[index] = null
}
// 通知指定的自定义事件(绑定的方法)执行
const emit = function emit(name, ...params) {
    if (typeof name !== 'string') throw new TypeError('name is not a string')
    let arr = listeners[name]
    if (!Array.isArray(arr)) return
    for (let i = 0; i < arr.length; i++) {
        let callback = arr[i]
        if (typeof callback !== 'function') {
            arr.splice(i, 1)
            i--
            continue
        }
        callback(...params)
    }
} 
*/

// ================测试
/* 
const fn1 = (x, y) => {
    console.log('fn1', x, y)
}
const fn2 = (x, y) => {
    console.log('fn2', x, y)
    _.off('AAA', fn1)
    _.off('AAA', fn2)
}
const fn3 = () => { console.log('fn3') }
const fn4 = () => { console.log('fn4') }
const fn5 = () => { console.log('fn5') }

_.on('AAA', fn1)
_.on('AAA', fn1)
_.on('AAA', fn2)
_.on('AAA', fn3)
_.on('AAA', fn4)
_.on('AAA', fn5)

document.body.onclick = function () {
    _.emit('AAA', 10, 20)
} 
*/

// ================
/* 
// 入口：需求，页面加载的2秒后，需要做一些事情
_.delay(2000).then(() => {
    _.emit('AAA')
})

// A模块
const fn1 = () => { console.log('fn1') }
_.on('AAA', fn1)

// B模块
const fn2 = () => { console.log('fn2') }
_.on('AAA', fn2)

// C模块
const fn3 = () => { console.log('fn3') }
_.on('AAA', fn3)

// ... 
*/